【java安全】动态代理

本文最后更新于:2023年9月9日 晚上

[TOC]

【java安全】动态代理

前言

java中代理分为两种:静态代理动态代理

而动态代理又分为:jdk动态代理CGLIB动态代理

image-20230716152714738

本文我们来谈谈jdk动态代理机制,位于java.lang.reflect.Proxy包下,动态代理的本质是通过反射来执行invoke()方法,动态执行方法

本质

Java 的动态代理机制是一种在运行时生成代理类和代理对象的机制,它允许在调用目标对象的方法前后插入额外的逻辑。动态代理本质上是基于反射来实现的。

在 Java 中,动态代理机制依赖于两个核心接口:java.lang.reflect.InvocationHandlerjava.lang.reflect.Proxy

  1. InvocationHandler 接口:它定义了一个单一的方法 invoke(),在代理对象的方法被调用时被触发。invoke() 方法接收三个参数:代理对象、被调用的方法对象以及传递给方法的参数。通过在 invoke() 方法中编写额外的逻辑,我们可以在方法调用前后执行自定义的代码。

  2. Proxy 类:它提供了创建代理对象的静态方法。Proxy.newProxyInstance() 方法是该类的主要方法,它接收一个类加载器、一组接口和一个 InvocationHandler 对象作为参数。调用该方法后,会动态生成一个代理类,并返回一个实现了指定接口的代理对象。

动态代理的本质可以概括如下:

  1. 在运行时生成代理类:Proxy.newProxyInstance() 方法利用传入的接口信息和 InvocationHandler 对象,在内存中动态生成一个代理类的字节码,并将其加载到 JVM 中。

  2. 代理对象的方法调用:当调用代理对象的方法时,实际上是通过反射调用 InvocationHandler 对象的 invoke() 方法。

  3. invoke() 方法中处理逻辑:在 invoke() 方法中,我们可以编写额外的逻辑,如记录日志、执行前置或后置操作等。然后,我们将真正的方法调用委托给原始的目标对象。

总结起来,Java 的动态代理机制本质上是利用反射生成了一个代理类并在运行时创建了代理对象。通过 InvocationHandler 接口的 invoke() 方法,我们可以在方法调用前后插入额外的逻辑来实现切面编程和其他功能。

重要方法

Proxy#newProxyInstance()

Proxy类中存在一个重要的newProxyInstance()方法,用于创建一个动态代理对象:

1
2
3
4
5
6
7
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
...
}

这个方法有三个参数:

  • ClassLoader loader 传入一个类加载器对象
  • Class<?>[] interfaces 传入一个InvocationHandler对象使用的接口的Class数组,用于指定创建的代理对象实现了哪些接口(通俗讲,就是规定代理对象有哪些方法,结构是什么样子的)
  • InvocationHandler h 传入一个InvocationHandler对象,实现了invoke()方法,规定了通过代理对象调用方法时的相关操作

InvocationHandler#invoke()

InvocationHandler接口中有一个抽象方法:invoke()

1
2
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;

参数:

  • proxy为代理对象
  • method为调用的方法对象
  • args为调用方法的参数

举例

假设杰伦要开演唱会,会表演唱歌和跳舞两个节目,那么在活动之前肯定要布置场地、检查门票,节目结束后要收拾场地等等。如果我们让杰伦来做这些事肯定是浪费时间的,我们需要叫一个中介(代理)来做这些事情

先定义一个接口,用于规定需要实现的方法(唱歌、跳舞):

1
2
3
4
interface Activity {
public String sing(String song);
public String dance(String name);
}

然后定义一个杰伦类,实现该接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Jay implements Activity {
private String name;

public Jay(String name) {
this.name = name;
}
@Override
public String sing(String song) {
System.out.println("正在布置场地~");
System.out.println("杰伦正在唱歌~");
return name + "正在唱" + song;
}

@Override
public String dance(String s) {
System.out.println("正在布置场地~");
System.out.println("杰伦正在跳舞~");
return name + "正在跳" + s;
}
}

然后我们测试一下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main {
public static void main(String[] args) {
Jay jay = new Jay("周杰伦");
String sing = jay.sing("晴天");
System.out.println(sing);
String dance = jay.dance("老年迪斯科");
System.out.println(dance);
}
}


/*
输出:
正在布置场地~
杰伦正在唱歌~
周杰伦正在唱晴天
正在布置场地~
杰伦正在跳舞~
周杰伦正在跳老年迪斯科
*/

我们现在需要使用代理来简化代码,不需要杰伦来布置,让中介(代理)来做这些事情

首先创建一个类Handler实现InvocationHandler接口,传入杰伦对象,实现invoke()方法,通过反射来调用杰伦对象中的方法(可以在调用方法前后添加一些额外的操作,此处我们可以添加布置场地的操作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Handler implements InvocationHandler {

private Jay jay;

public Handler(Jay jay) {
this.jay = jay;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object s;
if(method.getName().equals("sing")) {
System.out.println("布置唱歌场地~");
}else if(method.getName().equals("dance")) {
System.out.println("布置跳舞场地~");
}
s = method.invoke(jay,args);
return s;
}
}

这个类的作用就是创建对象后传入newProxyInstance()方法的参数中,在代理对象调用方法的时候会触发Handler对象的invoke()方法用反射来执行相关的方法

我们再主程序创建一个代理对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Main {
public static void main(String[] args) {

Handler handler = new Handler(new Jay("杰伦"));
Activity jayAgent = (Activity) Proxy.newProxyInstance(
Jay.class.getClassLoader(), Jay.class.getInterfaces(), handler
); //将Jay对象的类加载器、使用的接口、以及Handler对象传参
String sing = jayAgent.sing("七里香");
System.out.println(sing);
String dance = jayAgent.dance("老年迪斯科");
System.out.println(dance);
}
}

/*
输出
布置唱歌场地~
杰伦正在唱歌~
杰伦正在唱七里香
布置跳舞场地~
杰伦正在跳舞~
杰伦正在跳老年迪斯科
*/

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Main {
public static void main(String[] args) {
Handler handler = new Handler(new Jay("杰伦"));

Activity jayAgent = (Activity) Proxy.newProxyInstance(
Jay.class.getClassLoader(), Jay.class.getInterfaces(), handler
);
String sing = jayAgent.sing("七里香");
System.out.println(sing);
String dance = jayAgent.dance("老年迪斯科");
System.out.println(dance);
}
}

interface Activity {
public String sing(String song);
public String dance(String name);
}

class Jay implements Activity {
private String name;

public Jay(String name) {
this.name = name;
}
@Override
public String sing(String song) {
System.out.println("杰伦正在唱歌~");
return name + "正在唱" + song;
}

@Override
public String dance(String s) {
System.out.println("杰伦正在跳舞~");
return name + "正在跳" + s;
}
}


class Handler implements InvocationHandler {

private Jay jay;
public Handler(Jay jay) {
this.jay = jay;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object s;
if(method.getName().equals("sing")) {
System.out.println("布置唱歌场地~");
}else if(method.getName().equals("dance")) {
System.out.println("布置跳舞场地~");
}
s = method.invoke(jay,args);
return s;
}
}

可见,我们可以在Handler对象的invoke()调用方法前后加入一些额外的操作,通过动态代理可以简化原来的代码,并且减少原来代码的操作

https://xz.aliyun.com/t/9197#toc-6


【java安全】动态代理
https://leekosss.github.io/2023/08/24/[java安全]动态代理/
作者
leekos
发布于
2023年8月24日
更新于
2023年9月9日
许可协议